#include "HelperFunctions.h"
#include <string.h>
#include <malloc.h>
#include <stdlib.h>
#include <errno.h>

FILE *outputTarget = NULL;

int isKeyword(const Token *token, eTokenType expectedTokenTypeType);
int isSeparator(eTokenType tokenType);
int isEofExpected(eTokenType *followList);
int min3(int x, int y, int z);
int getStringDistance(const char *string, const char *target);

eType interpretNumericType(char *string)
{
	eType type;
	double value;

	if (strstr(string, "."))
	{
		type = real_Type;
		value = atof(string);
	}
	else
	{
		type = integer_Type;
		value = atoi(string);
	}

	if (value == 0 && errno == EINVAL)
	{
		type = none_Type;
	}

	return type;
}

// Performs a match between tokens and error recovery if possible.
int match(eTokenType tokenType, eTokenType *followList)
{
	Token *t = nextToken();
	int result = 0;

	if (t)
	{
		result = t->type == tokenType;
		if (!result)
		{
			eTokenType expectedTokenType[] = { tokenType, none_Token };

			// Attempt error recovery:

			// If we missed a seperator or misspelled a keyword, notify of a warning 
			// but continue parsing.
			// Otherwise go to the first token of the next variable.
			result = isSeparator(tokenType) | attemptRepairKeyword(t, tokenType);
			if (!result)
			{
				outputLexicalError(t, expectedTokenType);
				gotoFollow(followList);
			}
			else
			{
				outputLexicalWarning(t, expectedTokenType);
				backToken();
			}
		}
	}

	return result;
}

int matchType(eType expectedType, eType typeToCheck, const Token *token)
{
	int result;

	switch (expectedType)
	{
	case real_Type:
		result = typeToCheck != none_Type;
		break;
	case integer_Type:
		result = typeToCheck == integer_Type || typeToCheck == boolean_Type;
		break;
	case boolean_Type:
		result = typeToCheck == boolean_Type;
		break;
	default:
		result = 0;
		break;
	}

	if (!result)
	{
		// TODO: Print type mismatch error
		char buffer[1000] = { '\0' };

		sprintf(buffer, "Semantic Type Mismatch Error: Cannot convert from '%s' to '%s' at [%d, %d] (line, column).",
			typeStrings[typeToCheck], typeStrings[expectedType], token->lineNumber, token->columnNumber);
		outputLine(buffer);
	}

	return result;
}

int matchKinds(int expectedKind, eKind kindToCheck)
{
	// eKind enum is a flag list, if at least one kind matches,
	// we return true
	return expectedKind & kindToCheck;
}

int isDefined(SymbolTableEntry *entry, const Token *token)
{
	int result = entry != NULL;

	if (!result)
	{
		// TODO: Print undefined error
		char buffer[1000] = { '\0' };

		sprintf(buffer, "Semantic Type Undefined Error: '%s' is undefined, at [%d, %d] (line, column).",
			token->lexeme, token->lineNumber, token->columnNumber);
		outputLine(buffer);
	}

	return result;
}

int isRedefined(SymbolTableEntry *entry, const Token *token)
{
	int result = entry == NULL;

	if (result)
	{
		// TODO: Print redefined error
		char buffer[1000] = { '\0' };

		sprintf(buffer, "Semantic Redefinition Error: '%s' was redefined at [%d, %d] (line, column).",
			token->lexeme, token->lineNumber, token->columnNumber);
		outputLine(buffer);
	}

	return result;
}

int attemptRepairKeyword(Token *token, eTokenType expectedTokenType)
{
	int result = token->type == id_Token;

	if (result)
	{
		result = isKeyword(token, expectedTokenType);
		if (result)
		{
			token->type = expectedTokenType;
		}
	}

	return result;
}

// Checks whether what we have here is a misspelled keyword.
int isKeyword(const Token *token, eTokenType expectedTokenType)
{
	return getStringDistance(token->lexeme, tokenStrings[expectedTokenType]) <= 1;
}

// Checks whether the token is a seperator.
int isSeparator(eTokenType tokenType)
{
	const eTokenType *separators = tokenSeparators;
	int result = 0;

	for (; *separators; separators++)
	{
		if (*separators == tokenType)
		{
			result = 1;
			break;
		}
	}

	return result;
}

// Returns the minimum of 3 values.
int min3(int x, int y, int z)
{
    int temp = (x < y ? x : y);
    return (temp < z ? temp : z);
}

// Retrieves edit distance between two strings.
int getStringDistance(const char *string, const char *target)
{
	int **map;
    int i, j, temp,
		stringLength = strlen(string), 
		targetLength = strlen(target);

	map = (int**)malloc(sizeof(int*) * (stringLength + 1));
	for (i = 0; i <= stringLength; i++)
	{
		map[i] = (int*)malloc(sizeof(int) * (targetLength + 1));
	}

    map[0][0] = 0;
    for (i = 1; i <= stringLength; i ++)
	{
        map[i][0] = i;
	}

    for (j = 1; j <= targetLength; j ++)
	{
        map[0][j] = j;
	}

	for (i = 1; i <= stringLength; i ++) {
		for (j = 1; j <= targetLength; j ++) {
			if (string[i - 1] == target[j - 1])
				temp = 0;
			else
				temp = 1;
			map[i][j] = min3((map[i - 1][j - 1] + temp),
							(map[i][j - 1] + 1),
							(map[i - 1][j] + 1));
		}
	}
	
	temp = map[stringLength - 1][targetLength - 1];
	for (i = 0; i <= stringLength; i++)
	{
		free(map[i]);
	}

	free(map);
	return temp;
}

// Checks whether a token exists in a given follow list
int existsInFollow(eTokenType type, eTokenType *followList)
{
	int result = 0;

	for (; *followList; followList++)
	{
		if (*followList == type)
		{
			result = 1;
			break;
		}
	}

	return result;
}

// Returns true if EOF is expected in the given token list
int isEofExpected(eTokenType *followList)
{
	return existsInFollow(EOF_Token, followList);
}

// Skips input until we find a token from the supplied follow list.
void gotoFollow(eTokenType *followList)
{
	Token *t;
	eTokenType *follows;
	int result = 0;
	int eofExpected = isEofExpected(followList);
	
	if (followList)
	{
		do
		{
			follows = followList;
			t = nextToken();
			if (!eofExpected && t->type == EOF_Token)
			{
				fprintf(outputTarget, "Parsing Error: Unexpected end of file at line [%d, %d] (line, column).\n",
					t->lineNumber, t->columnNumber);
				exit(1);
			}
			else
			{
				for (; *follows && t; follows++)
				{
					if (*follows == t->type)
					{
						result = 1;
						break;
					}
				}
			}
		} while (!result && t && t->type != none_Token);

		backToken();
	}
}

void initializeOutput()
{
	outputTarget = stdout;
}

// Sets the out target, whether a file or the console
void setOutputTarget(FILE *target)
{
	outputTarget = target;
}

void outputLine(char *string)
{
	fprintf(outputTarget, "%s\n", string);
}

void outputRule(int rule)
{
	fprintf(outputTarget, "Parsing rule %d\n", rule);
}

void outputLexicalError(const Token *token, eTokenType *expectedTokenTypes)
{
	outputLexicalMessage("Error", token, expectedTokenTypes);
}

void outputLexicalWarning(const Token *token, eTokenType *expectedTokenTypes)
{
	outputLexicalMessage("Warning", token, expectedTokenTypes);
}

void outputLexicalMessage(const char *message, const Token *token, eTokenType *expectedTokenTypes)
{
	char tokenBuffer[1000] = { '\0' };
	const char *tokenLexeme = strlen(token->lexeme) ? token->lexeme : tokenStrings[token->type];;

	for (; *expectedTokenTypes; expectedTokenTypes++)
	{
		strcat(tokenBuffer, tokenStrings[*expectedTokenTypes]);
		strcat(tokenBuffer, ", ");
	}

	// Remove the last ", ".
	tokenBuffer[strlen(tokenBuffer) - 2] = '\0';

	fprintf(outputTarget, "Parsing %s: Expected token(s) '%s' actual token '%s' at [%d, %d] (line, column).\n",
		message, tokenBuffer, tokenLexeme, token->lineNumber, token->columnNumber);
	fflush(outputTarget);
}